/**
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, version 2.1, dated February 1999.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the latest version of the GNU Lesser General
 * Public License as published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program (LICENSE.txt); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.lightframework.jamwiki.servlets;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.jamwiki.DataAccessException;
import org.jamwiki.Environment;
import org.jamwiki.WikiBase;
import org.jamwiki.WikiMessage;
import org.jamwiki.model.Role;
import org.jamwiki.model.Topic;
import org.jamwiki.model.VirtualWiki;
import org.jamwiki.servlets.ServletUtil;
import org.jamwiki.servlets.ServletUtilEx;
import org.jamwiki.servlets.WikiPageInfo;
import org.jamwiki.utils.WikiUtil;
import org.lightframework.jamwiki.EnvironmentEx;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

/**
 * filter to replace {@link DispatcherServlet} for "not modifying the web.xml file when adding a wiki"
 * <p/>
 * add this filter to web.xml after spring security filter
 * 
 * @author fenghm(live.fenghm@gmail.com)
 */
public class DispatcherFilter extends DispatcherServlet implements Filter{

    private static final long serialVersionUID = 7903613037600006041L;

    public static final String CONTEXT_ATTRIBUTE = DispatcherFilter.class + ".CONTEXT";
    
	protected DispatchServletConfig servletConfig;
	
	protected UploadMappingServlet  uploadServlet;
	
	public static DispatcherFilter get(ServletContext context){
		return (DispatcherFilter)context.getAttribute(CONTEXT_ATTRIBUTE);
	}

	@SuppressWarnings("unchecked")
    public void init(FilterConfig config) throws ServletException {
		
    	Map<String, String> params = new HashMap<String, String>();
    	Enumeration names = config.getInitParameterNames();
    	while(names.hasMoreElements()){
    		String name  = (String)names.nextElement();
    		String value = config.getInitParameter(name);
    		
    		if(!name.startsWith("wiki:")){
    			params.put(name, config.getInitParameter(name));	
    		}else if(WikiUtil.isFirstUse()){
    			Environment.setValue(name.substring("wiki:".length()), value);
    		}
    	}

    	if(WikiUtil.isFirstUse()){
	    	Properties setup = EnvironmentEx.getSetupProperties();
	    	for(Object key : setup.keySet()){
	    		String value= setup.getProperty((String)key);
	    		Environment.setValue((String)key, value);
	    	}
    	}else{
    		//TODO : REVIEW THIS CODE
    		String contextPath = config.getServletContext().getContextPath();
    		String uploadPath  = Environment.getValue(Environment.PROP_FILE_DIR_RELATIVE_PATH);
    		if(!uploadPath.startsWith(contextPath)){
    			uploadPath = contextPath + uploadPath.substring(uploadPath.indexOf("/",1));
    		}
    		Environment.setValue(Environment.PROP_FILE_DIR_RELATIVE_PATH, uploadPath);
    	}
    	
    	this.servletConfig = 
    		new DispatchServletConfig(config.getServletContext(),
    								  config.getInitParameterNames(),
    								  params);
    	
    	this.uploadServlet = new UploadMappingServlet();
    	
    	this.init();
	}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    	HttpServletRequest httpReq = new MockRequest((HttpServletRequest)request);
    	
    	String path = getRequestPath(httpReq);
    	
    	if(isUploadPage(httpReq)){
    		uploadServlet.service(request, response);
    	}else if(isSpecialTopic(httpReq,path) || isVritualWiki(httpReq,path)){
    		//remove anaoymous roles
    		handleAnaoymousRoles();
    		
    		//dispatch
    		try{
    			service(request, response);
    		}finally{
    			WikiContext.reset();
    		}
    	}else{
    		chain.doFilter(request, response);
    	}
    }
    
    protected boolean isUploadPage(HttpServletRequest request){
    	String path = Environment.getValue(Environment.PROP_FILE_DIR_RELATIVE_PATH);
    	if(path.endsWith("/")){
    		path= path.substring(0,path.length()-1);
    	}
    	return request.getRequestURI().startsWith(path);
    }
    
    protected void handleAnaoymousRoles(){
    	Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    	if(null != auth && auth instanceof AnonymousAuthenticationToken){
    		List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
    		Collection<GrantedAuthority> authorities =  auth.getAuthorities();
    		if(null != authorities){
    			for(GrantedAuthority authority : authorities){
    				String role = authority.getAuthority();
    				if(!Role.ROLE_EDIT_EXISTING.getAuthority().equals(role) &&
    				   !Role.ROLE_EDIT_NEW.getAuthority().equals(role) &&
    				   !Role.ROLE_UPLOAD.getAuthority().equals(role)){
    					list.add(authority);
    				}
    			}
    			//TODO: HARD CODE KEY 'jam35Wiki'
    			AnonymousAuthenticationToken jamwikiAuth = new AnonymousAuthenticationToken("jam35Wiki", auth.getPrincipal(), list);
    			jamwikiAuth.setDetails(auth.getDetails());
    			jamwikiAuth.setAuthenticated(auth.isAuthenticated());
    			SecurityContextHolder.getContext().setAuthentication(jamwikiAuth);    			
    		}
    	}
    }
    
    @Override
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    	WikiContext.setIfNotSet(request, mv);
    	if(!WikiUtil.isFirstUse() && WikiContext.get().isWikiRequest()){
    		if(!ServletUtilEx.isViewError(mv) && !WikiContext.isViewTopic() && ServletUtilEx.isPostBack(request)){
    		    String returnTo = WikiContext.returnto();
    	    	if(null != returnTo && !StringUtils.isBlank(returnTo)){
    	    		mv.setView(new RedirectView(returnTo,false));
    	    	}
    		}
        	mv.addObject("isSpecialWiki",WikiContext.wiki().isSpecial());
    	}
	    super.render(mv, request, response);
    }
    
    protected void extendTopicRender(ModelAndView mv,WikiPageInfo page,HttpServletRequest request) throws Exception{
    	Topic topic = (Topic)mv.getModel().get(ServletUtil.PARAMETER_TOPIC_OBJECT);
    	if(null != topic){
    		if(topic.getReadOnly()){
    			LinkedHashMap<String, WikiMessage> menus = new LinkedHashMap<String, WikiMessage>();
    			for(String linkUrl : page.getTabMenu().keySet()){
    				if((!linkUrl.startsWith("Special:") && !linkUrl.startsWith("Comments:")) || linkUrl.startsWith("Special:Manage") || linkUrl.startsWith("Special:Print")){
    					menus.put(linkUrl, page.getTabMenu().get(linkUrl));
    				}
    			}
    			page.setTabMenu(menus);
    		}
    	}
    }
    
	@Override
    protected void initFrameworkServlet() throws ServletException {
		getServletContext().setAttribute(CONTEXT_ATTRIBUTE, this);
    }

	@Override
    public ServletConfig getServletConfig() {
	    return servletConfig;
    }
	
	private String getRequestPath(HttpServletRequest request){
		String uri     = request.getRequestURI();
		String context = ServletUtilEx.getContextPath(request);
		String path    = uri.substring(context.length());
		return path;
	}
	
	private boolean isSpecialTopic(HttpServletRequest request,String path) throws ServletException{
		//TODO : HARD CODE Special Topic Logic 
		String topic = WikiUtil.getTopicFromURI(request);
		if(null != topic && topic.startsWith("Special:")){
			return true;
		}
		return false;
	}

	private boolean isVritualWiki(HttpServletRequest request,String path) throws ServletException{
		
		if(WikiUtil.isFirstUse()){
			return false;
		}
		
		String name    = null;
		
		if(!"".equals(path)){
			int index = path.indexOf("/",1);
			if(index > 0){
				name = path.substring(1,index);
			}else{
				name = path.substring(1);
			}
		}
		
		if(null != name){
			try{
				List<VirtualWiki> wikis = WikiBase.getDataHandler().getVirtualWikiList();
				for(VirtualWiki wiki : wikis){
					if(wiki.getName().equals(name)){
						return true;
					}
				}
			}catch(DataAccessException e){
				throw new ServletException("error get virtual wikis",e);
			}
		}
		return false;
	}
	
	@SuppressWarnings("unchecked")
	private static final class DispatchServletConfig implements ServletConfig{
		
		private ServletContext servletContext;
		
		private Enumeration<String> paramNames;
		
		private Map<String, String> params;
		
		private DispatchServletConfig(ServletContext servletContext,Enumeration paramNames,Map<String,String> params){
			this.servletContext = servletContext;
			this.paramNames     = paramNames;
			this.params      = params;
		}

		public String getInitParameter(String name) {
	        return params.get(name);
        }

		public Enumeration getInitParameterNames() {
			return paramNames;
        }

		public ServletContext getServletContext() {
	        return servletContext;
        }

		public String getServletName() {
	        return "jamwiki";
        }
	}
	
	private static final class MockRequest extends HttpServletRequestWrapper{

		public MockRequest(HttpServletRequest request) {
	        super(request);
        }

		@Override
        public Locale getLocale() {
	        //TODO : HARD CODE zh_CN Locale
	        return Locale.SIMPLIFIED_CHINESE;
        }
		
	}
}
